package com.wang.transfer.util.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 主线程等待子线程结束，继续执行
 */
public class ThreadMainWait {

    private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 100L,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy());

    private static final ThreadPoolExecutor executor1 = new ThreadPoolExecutor(10, 10, 100L,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy());


    public static void main(String[] args) throws InterruptedException, BrokenBarrierException, ExecutionException {
//        useCountDownLatch();
//        useCyclicBarrier1();
//        useCyclicBarrier2();
//        useFuture();
        useCompletable();
    }

    // 1、使用CountDownLatch
    public static void useCountDownLatch() throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch count = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int index = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(200);
                    System.out.println("子线程" + index);
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        count.await();
        System.out.println("主线程---");
        long end = System.currentTimeMillis();
        System.out.println("花费时间：" + (end - start));
        executor.shutdown();
    }

    // 2、同步屏障CyclicBarrier
    // 2.1、CyclicBarrier使用
    /**
     * 1、屏障拦截数要算上主线程即：屏障拦截数量 = 子线程数量 + 主线程数量(11 = 10 + 1)
     * 2、主线程也要调用await()
     * 3、主线程会等待所有子线程到达屏障（调用await()）
     * 4、主线程无法控制子线程到达屏障（调用await()）后的操作，所以子线程调用await()要放在最后
     * 5、如果使用线程池，线程池核心线程数要大于等于屏障拦截数
     */
    public static void useCyclicBarrier1() throws InterruptedException, BrokenBarrierException {
        long start = System.currentTimeMillis();
        CyclicBarrier barrier = new CyclicBarrier(11);
        for (int i = 0; i < 10; i++) {
            int index = i;
            executor1.submit(() -> {
                try {
                    Thread.sleep(200);
                    System.out.println("子线程running" + index);
                    barrier.await();
                    System.out.println("子线程end***" + index);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        System.out.println("主线程running@@@");
        barrier.await();
        System.out.println("主线程end---");
        long end = System.currentTimeMillis();
        System.out.println("花费时间：" + (end - start));
        executor1.shutdown();
    }

    // 2.2、CyclicBarrier复用
    public static void useCyclicBarrier2() {
        long start = System.currentTimeMillis();
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            System.out.println("主线程end---");
            long end = System.currentTimeMillis();
            System.out.println("花费时间：" + (end - start));
        });
        for (int i = 0; i < 10; i++) {
            int index = i;
            executor.submit(() -> {
                try {
                    Thread.sleep(200);
                    System.out.println("子线程running" + index);
                    cyclicBarrier.await();
                    System.out.println("子线程end" + index);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }

    // 3、使用Future.get()
    public static void useFuture() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        List<Future> futureList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int index = i;
            Future future = executor.submit(() -> {
                try {
                    Thread.sleep(200);
                    System.out.println("子线程running" + index);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            futureList.add(future);
        }
        for (Future future: futureList) {
            // 监听线程池子线程执行状态及执行结果
            future.get();
        }
        System.out.println("主线程end---");
        long end = System.currentTimeMillis();
        System.out.println("花费时间：" + (end - start));
        executor.shutdown();
    }

    // 4、使用Completable.allOf()
    public static void useCompletable() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        List<CompletableFuture<String>> futureList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int index = i;
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
               try {
                   Thread.sleep(200);
               } catch (Exception e) {
                   e.printStackTrace();
               }
               // 模拟两次异常
                if (index % 7 == 0) {
                    int tmp = index / 0;
                }
                System.out.println("子程序running" + index);
                return "success";
            }, executor).exceptionally(Throwable::getMessage);
            futureList.add(future);
        }
        // allOf()等待所有线程执行完毕
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));

        // 但是allOf()拿不到执行结果，需要再次回调获取结果
        CompletableFuture<List<String>> finalFutures = allFutures.thenApply(v -> futureList.stream().map(CompletableFuture::join).collect(Collectors.toList()));
        System.out.println(finalFutures.get());
        // 也可以直接使用最初的futureList集合获取结果，如下两行：
//        List<String> resultList = futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
//        System.out.println(resultList);
        System.out.println("主线程end---");
        long end = System.currentTimeMillis();
        System.out.println("花费时间：" + (end - start));
        executor.shutdown();
    }
}
